/**
 * 
 */
package pacMan;

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.util.ArrayList;

/**
 * Class: Ghost
 * 
 * Subclass of Actor, this defines an animated movable BasicDrawableObject that can
 * independently determine where and when to move in a particular direction. Two types
 * of movement algorithms have been instituted: one where the Ghost will wander in a
 * particular area in a set path, and one where the Ghost will actively track and chase
 * the PlayerObject.
 * 
 * @author 		Tomas Rivera
 * @version 	$Id$
 *
 */
public class Ghost extends Actor {
	
	//////////////////////////////////////////////////////////
	// Constructor methods
	//////////////////////////////////////////////////////////
	
	/**
	 * Creates a Ghost object, which is an enemy Actor that can chase and "consume"
	 * the PlayerObject.
	 * 
	 * @param c
	 * @param w
	 * @param pValue
	 * @param objectLocationTracker
	 * @param color
	 * @param name
	 * @param targetTileX
	 * @param targetTileY
	 */
	@SuppressWarnings("unchecked")
	public Ghost(Coordinate c, WindowParameters w, int pValue, ObjectLocationTracker objectLocationTracker,
			Color color, String name, int targetTileX, int targetTileY) {
		super(c, w, ScoreTracker.GHOST_POINT_VALUE, objectLocationTracker);
		this.name = name;
		textFont = new Font("Lucida Console", Font.BOLD, 12);
		xLegPolygonPoints = new ArrayList[2];
		yLegPolygonPoints = new ArrayList[2];
		xLegPolygonPoints[0] = new ArrayList<Integer>();
		xLegPolygonPoints[1] = new ArrayList<Integer>();
		yLegPolygonPoints[0] = new ArrayList<Integer>();
		yLegPolygonPoints[1] = new ArrayList<Integer>();
		mouthStartPointsX = new ArrayList<Integer>();
		mouthEndPointsX = new ArrayList<Integer>();
		mouthStartPointsY = new ArrayList<Integer>();
		mouthEndPointsY = new ArrayList<Integer>();
		bodyColor = color;
		invertScaredColors = false;
		legPositionCounter = 0;
		legPosition = false;
		partner = null;
		
		setDirection(CardinalDirection.SOUTH);
		defaultTarget = new Coordinate((targetTileX * this.getWindowParams().getPixelScale()) + this.getWindowParams().getPixelOffsetX(),
				(targetTileY * this.getWindowParams().getPixelScale()) + this.getWindowParams().getPixelOffsetY(), this.getWindowParams());
		this.targetXCo = this.defaultTarget.getX();
		this.targetYCo = this.defaultTarget.getY();
		
		// Set the ghost position over 1/2 a tile.
		c.changePosition(this.getObjectLocation().getX() + (this.getWindowParams().getPixelScale() / 2), this.getObjectLocation().getY());
		this.setHomeLocation(c);
		
		// Blinky is a special case, he starts each round outside of the ghost pen.
		if(name.equals("Blinky")) {
			setDirection(CardinalDirection.WEST);
			setMoveMode(GameStateManager.NAV_MODE.WANDER_MAZE);
		} else {
			setMoveMode(GameStateManager.NAV_MODE.PACE_HOME);
		}
		
		objectLocationTracker.registerEnemyActor(this);
		
		setHazardous(true);
		setEdible(false);
		
		this.resizeObject();
	}
	
	//////////////////////////////////////////////////////////
	// Public / Protected methods
	//////////////////////////////////////////////////////////
	
	
	/**
	 * For Ghosts that need to base their movements on the position of another Ghost.
	 * 
	 * @param ghost
	 */
	public void setPartner(Ghost ghost) { partner = ghost; }
	
	/**
	 * Check if the Ghost is currently in 'frightened' state.
	 * 
	 * @return
	 */
	public boolean isScared() { return (!hazardous() && edible()); }
	
	/**
	 * Set object so that the Ghost should be drawn with alternate color scheme. This is
	 * used to have the character "flash" and warn the player when the frightened timer
	 * is close to expiring.
	 * 
	 * @param invert
	 */
	public void invertScaredColors(boolean invert) { this.invertScaredColors = invert; }

	/**
	 * Check if the Ghost was consumed by the PlayerObject. In this state, the Ghost
	 * will be drawn on screen as just a pair of eyes.
	 * 
	 * @return
	 */
	public boolean wasEatenByPlayer() { return (!hazardous() && !edible()); }
	
	/**
	 * Get the String name of the Ghost, i.e. Blinky, Pinky, etc.
	 * 
	 * @return
	 */
	public String getName() { return name; }
	
	/**
	 * Get the current navigation movement mode for the Ghost, i.e. WANDER_MAZE,
	 * PACE_HOME, etc.
	 * 
	 * @return
	 */
	public GameStateManager.NAV_MODE getMoveMode() { return ghostMovementMode; }
	
	/**
	 * Change the navigation movement mode of the Ghost.
	 * 
	 * @param mode
	 */
	public void setMoveMode(GameStateManager.NAV_MODE mode) {
		ghostMovementMode = mode;
		System.out.printf("%s movement mode is %s%n", this.getName(), this.getMoveMode());
	}
	
	
	/**
	 * Change the Ghost movement target tile to the grid location specified.
	 * 
	 * @param x
	 * @param y
	 */
	public void forceNewTarget(int x, int y) {
		this.targetXCo = x;
		this.targetYCo = y;
	}
	
	//////////////////////////////////////////////////////////
	// Overridden methods
	//////////////////////////////////////////////////////////
	
	/*
	 * (non-Javadoc)
	 * @see pacMan.ConsumableObject#consumeObject(pacMan.Actor)
	 */
	@Override
	public void consumeObject(Actor consumer) {
		// TODO Auto-generated method stub
		consumer.addToScore(this.getPointValue(), true);
		this.setEatenByPlayer();
	}

	/*
	 * (non-Javadoc)
	 * @see pacMan.BasicDrawableObject#drawObject(java.awt.Graphics2D)
	 */
	@Override
	public void drawObject(Graphics2D g2d) {
		
		if(hideObject() || !visible()) { return; }
		
		int i;
		int offsetX = this.getObjectLocation().getX() - (getWindowParams().getActorScale() / 2);
        int offsetY = this.getObjectLocation().getY() - (getWindowParams().getActorScale() / 2);
        
        g2d.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY);
        g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        g2d.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY);
        g2d.setRenderingHint(RenderingHints.KEY_DITHERING, RenderingHints.VALUE_DITHER_ENABLE);
        g2d.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON);
        g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
        g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
        g2d.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE);
		
		if(edible() || hazardous()) {
			int polyType;
			
	        if (isScared()) {
	        	if(invertScaredColors) {
					g2d.setColor(Color.WHITE);
				} else {
					g2d.setColor(Color.BLUE);
				}
	        }
	        else {
	        	g2d.setColor(bodyColor);
	        }
	        
	        // Top portion of Ghost is a half-circle
	        g2d.fillArc(offsetX, offsetY,
	        		this.getWindowParams().getActorScale(),
	        		this.getWindowParams().getActorScale(), 
	        		0, 180);
	        
	        if((legPositionCounter++ % 15) == 0) {
	        	legPosition = !legPosition;
	        }
	        
	        if(legPosition) {
	        	polyType = 0;
	        } else {
	        	polyType = 1;
	        }
	        
	        
	        
	        int xPoly[] = new int[13];
	        int yPoly[] = new int[13];
	        
	        i = 0;
	        for(Integer elem : xLegPolygonPoints[polyType]) {
	        	xPoly[i] = elem + offsetX;
	        	i++;
	        }
	        
	        i = 0;
	        for(Integer elem : yLegPolygonPoints[polyType]) {
	        	yPoly[i] = elem + offsetY;
	        	i++;
	        }
	        
	        g2d.fillPolygon(xPoly, yPoly, 13);
		}
		
		
		if(isScared()) { 
        	// Eyes
			if(invertScaredColors) {
				g2d.setColor(Color.RED);
			} else {
				g2d.setColor(Color.WHITE);
			}
        	g2d.fillOval(offsetX + scaredLeftEyeX, offsetY + leftEyeY, scaredEyesWidth, scaredEyesHeight);
	        g2d.fillOval(offsetX + scaredRightEyeX, offsetY + rightEyeY, scaredEyesWidth, scaredEyesHeight);
        	
        	// Mouth	        
	        g2d.setStroke(new BasicStroke(this.getWindowParams().getActorScale() / 20));
	        
	        for(i = 0; i < mouthStartPointsX.size(); i++) {
	        	g2d.drawLine(offsetX + mouthStartPointsX.get(i), offsetY + mouthStartPointsY.get(i),
	        			offsetX + mouthEndPointsX.get(i), offsetY + mouthEndPointsY.get(i));
	        }

        } else {
	        // Eyes
	        g2d.setColor(Color.WHITE);
	        g2d.fillOval(offsetX + leftEyeX, offsetY + leftEyeY - _eyeUpShift[getDirection().getValue() - 37], eyeWidth, eyeHeight);
	        g2d.fillOval(offsetX + rightEyeX, offsetY + rightEyeY - _eyeUpShift[getDirection().getValue() - 37], eyeWidth, eyeHeight);
	        
	        // Pupils
	        g2d.setColor(Color.BLACK);
	        g2d.fillOval(offsetX + leftEyeX + xPupilOffset[getDirection().getValue() - 37], 
	        		offsetY + leftEyeY + yPupilOffset[getDirection().getValue() - 37] - _eyeUpShift[getDirection().getValue() - 37],
	        		eyeWidth / 2, eyeWidth / 2);
	        g2d.fillOval(offsetX + rightEyeX + xPupilOffset[getDirection().getValue() - 37], 
	        		offsetY + rightEyeY + yPupilOffset[getDirection().getValue() - 37] - _eyeUpShift[getDirection().getValue() - 37], 
	        		eyeWidth / 2, eyeWidth / 2);
        }
		
		//printDebugInfoOnScreen(g2d);

	}
	
	/*
	 * (non-Javadoc)
	 * @see pacMan.BasicDrawableObject#resizeObject()
	 */
	@Override
	public void resizeObject() {
		this.getObjectLocation().update(this.getWindowParams().getWindowWidth(), this.getWindowParams().getWindowHeight());
		this.defaultTarget.update(this.getWindowParams().getWindowWidth(), this.getWindowParams().getWindowHeight());
		int actorSize = this.getWindowParams().getActorScale();
		
		// Purge the arrays
		xLegPolygonPoints[0].clear();
		xLegPolygonPoints[1].clear();
		yLegPolygonPoints[0].clear();
		yLegPolygonPoints[1].clear();
		mouthStartPointsX.clear();
		mouthEndPointsX.clear();
		mouthStartPointsY.clear();
		mouthEndPointsY.clear();
	        
        radius = actorSize / 2;
        legYTop = actorSize - (actorSize / 6);
        leftEyeX = actorSize / 6;
        leftEyeY = actorSize * 4 / 15;
        rightEyeX = actorSize * 17 / 30;
        rightEyeY = leftEyeY;
        eyeWidth = (actorSize * 4) / 15;
        eyeHeight = actorSize / 3;
        
        // Pupils pointing West
    	xPupilOffset[CardinalDirection.WEST.getValue() - 37] = 0;
    	yPupilOffset[CardinalDirection.WEST.getValue() - 37] = eyeHeight / 3;
    	_eyeUpShift[CardinalDirection.WEST.getValue() - 37] = 0;
    	
    	// Pupils pointing North
    	xPupilOffset[CardinalDirection.NORTH.getValue() - 37] = eyeWidth / 4;
    	yPupilOffset[CardinalDirection.NORTH.getValue() - 37] = 0;
    	_eyeUpShift[CardinalDirection.NORTH.getValue() - 37] = (actorSize * 2) / 15;
    	
    	// Pupils pointing East
    	xPupilOffset[CardinalDirection.EAST.getValue() - 37] = eyeWidth / 2;
    	yPupilOffset[CardinalDirection.EAST.getValue() - 37] = eyeHeight / 3;
    	_eyeUpShift[CardinalDirection.EAST.getValue() - 37] = 0;
    	
    	// Pupils pointing South
    	xPupilOffset[CardinalDirection.SOUTH.getValue() - 37] = eyeWidth / 4;
    	yPupilOffset[CardinalDirection.SOUTH.getValue() - 37] = eyeHeight * 2 / 3;
    	_eyeUpShift[CardinalDirection.SOUTH.getValue() - 37] = 0;
        
        
        xLegPolygonPoints[0].add(0);
        xLegPolygonPoints[0].add(0);
        xLegPolygonPoints[0].add((actorSize * 2) / 15);
        
        xLegPolygonPoints[0].add((actorSize * 4) / 15);
        xLegPolygonPoints[0].add((actorSize * 6) / 15);
        xLegPolygonPoints[0].add((actorSize * 6) / 15);
        
        xLegPolygonPoints[0].add((actorSize * 9) / 15);
        xLegPolygonPoints[0].add((actorSize * 9) / 15);
        xLegPolygonPoints[0].add((actorSize * 11) / 15);
        
        xLegPolygonPoints[0].add((actorSize * 13) / 15);
        xLegPolygonPoints[0].add(actorSize);
        xLegPolygonPoints[0].add(actorSize);
        
        xLegPolygonPoints[0].add(0);
        
        
        xLegPolygonPoints[1].add(0);
        xLegPolygonPoints[1].add(0);
        xLegPolygonPoints[1].add((actorSize / 15));
        
        xLegPolygonPoints[1].add(((actorSize * 3) / 15));
        xLegPolygonPoints[1].add(((actorSize * 9) / 30));
        xLegPolygonPoints[1].add(((actorSize * 13) / 30));
        
        xLegPolygonPoints[1].add(((actorSize * 17) / 30));
        xLegPolygonPoints[1].add(((actorSize * 21) / 30));
        xLegPolygonPoints[1].add(((actorSize * 25) / 30));
        
        xLegPolygonPoints[1].add(((actorSize * 14) / 15));
        xLegPolygonPoints[1].add(actorSize);
        xLegPolygonPoints[1].add(actorSize);
        
        xLegPolygonPoints[1].add(0);
        
        
        yLegPolygonPoints[0].add(radius);
        yLegPolygonPoints[0].add(actorSize);
        yLegPolygonPoints[0].add(legYTop);
        
        yLegPolygonPoints[0].add(actorSize);
        yLegPolygonPoints[0].add(actorSize);
        yLegPolygonPoints[0].add(legYTop);
        
        yLegPolygonPoints[0].add(legYTop);
        yLegPolygonPoints[0].add(actorSize);
        yLegPolygonPoints[0].add(actorSize);
        
        yLegPolygonPoints[0].add(legYTop);
        yLegPolygonPoints[0].add(actorSize);
        yLegPolygonPoints[0].add(radius);
        
        yLegPolygonPoints[0].add(radius);
        
        yLegPolygonPoints[1].add(radius);
        yLegPolygonPoints[1].add(legYTop + (actorSize / 15));
        yLegPolygonPoints[1].add(actorSize);
        
        yLegPolygonPoints[1].add(actorSize);
        yLegPolygonPoints[1].add(legYTop);
        yLegPolygonPoints[1].add(actorSize);
        
        yLegPolygonPoints[1].add(actorSize);
        yLegPolygonPoints[1].add(legYTop);
        yLegPolygonPoints[1].add(actorSize);
        
        yLegPolygonPoints[1].add(actorSize);
        yLegPolygonPoints[1].add(legYTop + (actorSize / 15));
        yLegPolygonPoints[1].add(radius);
        
        yLegPolygonPoints[1].add(radius);
        
        mouthStartPointsX.add(actorSize * 3 / 30);
        mouthStartPointsX.add(actorSize * 6 / 30);
        mouthStartPointsX.add(actorSize * 9 / 30);
        mouthStartPointsX.add(actorSize * 12 / 30);
        
        mouthStartPointsX.add(actorSize * 15 / 30);
        mouthStartPointsX.add(actorSize * 18 / 30);
        mouthStartPointsX.add(actorSize * 21 / 30);
        mouthStartPointsX.add(actorSize * 24 / 30);
        
        mouthEndPointsX.add(actorSize * 6 / 30);
        mouthEndPointsX.add(actorSize * 9 / 30);
        mouthEndPointsX.add(actorSize * 12 / 30);
        mouthEndPointsX.add(actorSize * 15 / 30);
        
        mouthEndPointsX.add(actorSize * 18 / 30);
        mouthEndPointsX.add(actorSize * 21 / 30);
        mouthEndPointsX.add(actorSize * 24 / 30);
        mouthEndPointsX.add(actorSize * 27 / 30);
        
        mouthStartPointsY.add(actorSize * 3 / 4);
        mouthStartPointsY.add(actorSize * 2 / 3);
        mouthStartPointsY.add(actorSize * 3 / 4);
        mouthStartPointsY.add(actorSize * 2 / 3);
        mouthStartPointsY.add(actorSize * 3 / 4);
        mouthStartPointsY.add(actorSize * 2 / 3);
        mouthStartPointsY.add(actorSize * 3 / 4);
        mouthStartPointsY.add(actorSize * 2 / 3);
        
        mouthEndPointsY.add(actorSize * 2 / 3);
        mouthEndPointsY.add(actorSize * 3 / 4);
        mouthEndPointsY.add(actorSize * 2 / 3);
        mouthEndPointsY.add(actorSize * 3 / 4);
        mouthEndPointsY.add(actorSize * 2 / 3);
        mouthEndPointsY.add(actorSize * 3 / 4);
        mouthEndPointsY.add(actorSize * 2 / 3);
        mouthEndPointsY.add(actorSize * 3 / 4);
        
        
        scaredLeftEyeX = (actorSize - (rightEyeX + (eyeWidth * 2 / 3) - leftEyeX)) / 2;
    	scaredRightEyeX = actorSize - ((actorSize - (rightEyeX + (eyeWidth * 2 / 3) - leftEyeX)) / 2) - (eyeWidth * 2 / 3);
    	scaredEyesWidth = eyeWidth * 2 / 3;
    	scaredEyesHeight = eyeHeight * 2 / 3;
	}
	
	/*
	 * (non-Javadoc)
	 * @see pacMan.Actor#moveActor(pacMan.GameStateManager.MOVE_BEHAVIOR)
	 */
	@Override
	public GameStateManager.GAME_EVENT moveActor(GameStateManager.MOVE_BEHAVIOR ghostGlobalState) {

		setMoveTarget(objectLocationTracker.getPlayerLocation(), objectLocationTracker.getPlayerDirection(), ghostGlobalState);
		
		if(getMoveMode() == GameStateManager.NAV_MODE.WANDER_MAZE ||
		   getMoveMode() == GameStateManager.NAV_MODE.GO_HOME) {
			if(isScared()) {
				if(isCenteredHorizontally() && isCenteredVertically()) {
					// Randomly choose a direction to move in
					while(true) {
						CardinalDirection direction = CardinalDirection.pickRandomDirection();
						if(direction != getDirection().opposite() && objectLocationTracker.legalMove(this, direction)) {
							moveActor(direction, 1);
							break;
						}
					}
				} else {
					moveActor(1);
				}
			} else {
				if(isCenteredHorizontally() && isCenteredVertically()) {
					// Pick whatever valid direction there is that gets us close to our target
					CardinalDirection bestMoveDirection = getDirection();
					int shortestDistance = 999999;
					int distance = 0;
					
					if(getDirection() != CardinalDirection.SOUTH &&
						objectLocationTracker.legalMove(this, CardinalDirection.NORTH) &&
						objectLocationTracker.isEnemyUpwardMovementAllowed(this))
					{
						// sqrt((p1 - q1)^2 + (p2 - q2)^2)
						distance = (int) Math.sqrt(Math.pow((getTargetXCo() - getObjectLocation().getX()), 2) +
								Math.pow((getTargetYCo() - (getObjectLocation().getY() - getWindowParams().getPixelScale())), 2));
						
						if(distance < shortestDistance) {
							shortestDistance = distance;
							bestMoveDirection = CardinalDirection.NORTH;
						}
						
						//System.out.printf("moveGhost() (%s) valid move: %s, distance to target %d%n", getName(), CardinalDirection.NORTH, distance);
					}
					
					if(getDirection() != CardinalDirection.EAST &&
							objectLocationTracker.legalMove(this, CardinalDirection.WEST)) 
					{
						distance = (int) Math.sqrt(Math.pow((getTargetXCo() - (getObjectLocation().getX() - getWindowParams().getPixelScale())), 2) +
								Math.pow((getTargetYCo() - getObjectLocation().getY()), 2));
						
						if(distance < shortestDistance) {
							shortestDistance = distance;
							bestMoveDirection = CardinalDirection.WEST;
						}
						
						//System.out.printf("moveGhost() (%s) valid move: %s, distance to target %d%n", getName(), CardinalDirection.WEST, distance);
					}
					
					if(getDirection() != CardinalDirection.NORTH &&
							objectLocationTracker.legalMove(this, CardinalDirection.SOUTH)) 
					{
						distance = (int) Math.sqrt(Math.pow((getTargetXCo() - getObjectLocation().getX()),  2) +
								Math.pow((getTargetYCo() - (getObjectLocation().getY() + getWindowParams().getPixelScale())), 2));
						
						if(distance < shortestDistance) {
							shortestDistance = distance;
							bestMoveDirection = CardinalDirection.SOUTH;
						}
						
						//System.out.printf("moveGhost() (%s) valid move: %s, distance to target %d%n", getName(), CardinalDirection.SOUTH, distance);
					} 
					
					if(getDirection() != CardinalDirection.WEST &&
							objectLocationTracker.legalMove(this, CardinalDirection.EAST)) 
					{
						distance = (int) Math.sqrt(Math.pow((getTargetXCo() - (getObjectLocation().getX() + getWindowParams().getPixelScale())), 2) +
								Math.pow((getTargetYCo() - getObjectLocation().getY()), 2));
						
						if(distance < shortestDistance) {
							shortestDistance = distance;
							bestMoveDirection = CardinalDirection.EAST;
						}
						
						//System.out.printf("moveGhost() (%s) valid move: %s, distance to target %d%n", getName(), CardinalDirection.EAST, distance);
					}
					
					moveActor(bestMoveDirection, 1);
				} else if(getMoveMode() == GameStateManager.NAV_MODE.GO_HOME && atFrontDoor()) {
					setMoveMode(GameStateManager.NAV_MODE.ENTER_HOME);
					moveActor(CardinalDirection.SOUTH, 0);
					
				} else {
					moveActor(1);
				}
			}
		} else {
			if(getMoveMode() == GameStateManager.NAV_MODE.PACE_HOME) {
				//System.out.printf("moveGhost() (%s) yCo=%d, yTargetCo=%d%n", getName(), getPixelYLocation(), getTargetYCo() + this.windowParams.getPixelOffsetY());
				// Allowed to pace up and down in the pen
				setFrozen(1);
				if(getPixelYLocation() == (getTargetYCo() + (getWindowParams().getPixelScale() / 2))) {
					moveActor(CardinalDirection.NORTH, 1);
					return GameStateManager.GAME_EVENT.NONE;
				} else if(getPixelYLocation() == (getTargetYCo() - (getWindowParams().getPixelScale() / 2))) {
					moveActor(CardinalDirection.SOUTH, 1);
					return GameStateManager.GAME_EVENT.NONE;
				} else {
					moveActor(1);
				}
			} else if(getMoveMode() == GameStateManager.NAV_MODE.LEAVE_HOME) {
				moveActor(CardinalDirection.NORTH, 1);
				if(getTargetYCo() == getPixelYLocation()) {
					setMoveMode(GameStateManager.NAV_MODE.WANDER_MAZE);
					moveActor(CardinalDirection.WEST, 0);
				}
				
			} else if(getMoveMode() == GameStateManager.NAV_MODE.GO_HOME_CENTER) {
				setFrozen(1);
				if(getPixelXLocation() == getTargetXCo()) {
					moveActor(CardinalDirection.NORTH, 1);
					setMoveMode(GameStateManager.NAV_MODE.LEAVE_HOME);
				} else if (getPixelXLocation() < getTargetXCo()){
					moveActor(CardinalDirection.EAST, 1);
					
				} else {
					moveActor(CardinalDirection.WEST, 1);
				}
				
			} else if(getMoveMode() == GameStateManager.NAV_MODE.ENTER_HOME) {
				setFrozen(1);
				if(getPixelXLocation() == getTargetXCo() &&
					getPixelYLocation() == getTargetYCo()) {
					moveActor(CardinalDirection.NORTH, 1);
					setMoveMode(GameStateManager.NAV_MODE.LEAVE_HOME);
					clearEatenByPlayer();
					return GameStateManager.GAME_EVENT.GHOST_REACHED_HOME;
				} else {
					moveActor(CardinalDirection.SOUTH, 1);
				}
			} else {
				assert false;
			}
		}
		
		// Check if we're on the same tile as the player.
		if(hazardous() && objectLocationTracker.checkForEnemyPlayerCollision(this)) {
			return GameStateManager.GAME_EVENT.PLAYER_KILLED;
		} else {
			return GameStateManager.GAME_EVENT.NONE;
		}
	}
	
	/*
	 * (non-Javadoc)
	 * @see pacMan.Actor#addToScore(int, boolean)
	 */
	@Override
	public void addToScore(int pointValue, boolean isEnemyActor) { /* Do nothing */ }
	
	@Override
	public void returnActorToHomeLocation() {
		super.returnActorToHomeLocation();
			
		if(name.equals("Blinky")) {
			setDirection(CardinalDirection.WEST);
			setMoveMode(GameStateManager.NAV_MODE.WANDER_MAZE);
		} else {
			setDirection(CardinalDirection.SOUTH);
			setMoveMode(GameStateManager.NAV_MODE.PACE_HOME);
		}
	}
	
	//////////////////////////////////////////////////////////
	// Private methods
	//////////////////////////////////////////////////////////
	
	/*
	 * Specify a new movement target based on the global movement mode game state (SCATTER or CHASE), the current
	 * PlayerObject location and direction PlayerObject is facing.
	 *  
	 */
	private void setMoveTarget(Coordinate playerLocation, CardinalDirection playerDirection, GameStateManager.MOVE_BEHAVIOR globalMovementMode) {
		
		if(ghostMovementMode == GameStateManager.NAV_MODE.WANDER_MAZE){
			if(isScared()) {
				this.targetXCo = this.getObjectLocation().getX();
				this.targetYCo = this.getObjectLocation().getY();
				return;
			}
			
			switch(globalMovementMode) {
				default:
				case CHASE:
					switch(name) {
						default:
						case "Blinky":
							this.targetXCo = playerLocation.getX();
							this.targetYCo = playerLocation.getY();
							break;
						case "Pinky":
							switch(playerDirection) {
								case NORTH:
									// Replicates bug in arcade game.
									this.targetXCo = playerLocation.getX() - (this.getWindowParams().getPixelScale() * 4);
									this.targetYCo = playerLocation.getY() - (this.getWindowParams().getPixelScale() * 4);
									break;
								case SOUTH:
									this.targetXCo = playerLocation.getX();
									this.targetYCo = playerLocation.getY() + (this.getWindowParams().getPixelScale() * 4);
									break;
								case EAST:
									this.targetXCo = playerLocation.getX() + (this.getWindowParams().getPixelScale() * 4);
									this.targetYCo = playerLocation.getY();
									break;
								case WEST:
									this.targetXCo = playerLocation.getX() - (this.getWindowParams().getPixelScale() * 4);
									this.targetYCo = playerLocation.getY();
									break;
								default:
									break;
							}
							break;
							
						case "Inky":
							if(partner != null) {
								// First figure out placement two tiles in front of player
								int xTarget = 0;
								int yTarget = 0;
								switch(playerDirection) {
									case NORTH:
										// Replicates bug in arcade game
										xTarget = playerLocation.getX() - (this.getWindowParams().getPixelScale() * 2);
										yTarget = playerLocation.getY() - (this.getWindowParams().getPixelScale() * 2);
										break;
									case SOUTH:
										xTarget = playerLocation.getX();
										yTarget = playerLocation.getY() + (this.getWindowParams().getPixelScale() * 4);
										break;
									case EAST:
										xTarget = playerLocation.getX() + (this.getWindowParams().getPixelScale() * 4);
										yTarget = playerLocation.getY();
										break;
									case WEST:
										xTarget = playerLocation.getX() - (this.getWindowParams().getPixelScale() * 4);
										yTarget = playerLocation.getY();
										break;
									default:
										break;
								}
								
								// Now use Blinky's position to calculate our target.
								if(partner.getPixelXLocation() < xTarget) {
									this.targetXCo = (xTarget - partner.getPixelXLocation()) + xTarget;
								} else {
									this.targetXCo = xTarget - (partner.getPixelXLocation() - xTarget);
								}
								
								if(partner.getPixelYLocation() < yTarget) {
									this.targetYCo = (yTarget - partner.getPixelYLocation()) + yTarget;
								} else {
									this.targetYCo = yTarget - (partner.getPixelYLocation() - yTarget);
								}
								
							} else {
								this.targetXCo = playerLocation.getX();
								this.targetYCo = playerLocation.getY();
							}
							break;
							
						case "Clyde":
							if((int) Math.sqrt(Math.pow(((double)playerLocation.getX() - (double)getObjectLocation().getX()), (double) 2) +
									Math.pow(((double)playerLocation.getY() - (double)getObjectLocation().getY()), (double) 2)) > (8 * this.getWindowParams().getPixelScale())) 
							{
								targetXCo = defaultTarget.getX();
								targetYCo = defaultTarget.getY();
							} 
							else 
							{
								this.targetXCo = playerLocation.getX();
								this.targetYCo = playerLocation.getY();
							}
							break;
					}
					
					break;
				
				
				case SCATTER:
					targetXCo = defaultTarget.getX();
					targetYCo = defaultTarget.getY();
					break;
				
			}
		} else {
			switch(ghostMovementMode) {
				case ENTER_HOME:
				case GO_HOME_CENTER:
					this.targetXCo = (13 * this.getWindowParams().getPixelScale()) + (this.getWindowParams().getPixelScale() / 2) + this.getWindowParams().getPixelOffsetX();
					this.targetYCo = (14 * this.getWindowParams().getPixelScale()) + this.getWindowParams().getPixelOffsetY();
					break;
				case GO_HOME:
				case LEAVE_HOME:
					this.targetXCo = (13 * this.getWindowParams().getPixelScale()) + (this.getWindowParams().getPixelScale() / 2) + this.getWindowParams().getPixelOffsetX();
					this.targetYCo = (11 * this.getWindowParams().getPixelScale()) + this.getWindowParams().getPixelOffsetY();
					break;
				case PACE_HOME:
					switch(name) {
						case "Pinky":
							this.targetXCo = (13 * this.getWindowParams().getPixelScale()) + (this.getWindowParams().getPixelScale() / 2) + this.getWindowParams().getPixelOffsetX();
							this.targetYCo = (14 * this.getWindowParams().getPixelScale()) + this.getWindowParams().getPixelOffsetY();
							break;
						case "Inky":
							this.targetXCo = (11 * this.getWindowParams().getPixelScale()) + (this.getWindowParams().getPixelScale() / 2) + this.getWindowParams().getPixelOffsetX();
							this.targetYCo = (14 * this.getWindowParams().getPixelScale()) + this.getWindowParams().getPixelOffsetY();
							break;
						case "Clyde":
							this.targetXCo = (15 * this.getWindowParams().getPixelScale()) + (this.getWindowParams().getPixelScale() / 2) + this.getWindowParams().getPixelOffsetX();
							this.targetYCo = (14 * this.getWindowParams().getPixelScale()) + this.getWindowParams().getPixelOffsetY();
							break;
						default:
							break;
					}
					break;
				default:
					assert false;
					break;
			}
		}
	}
	
	/*
	 * Set that the Ghost has been consumed by the PlayerObject, and perform any
	 * necessary related actions.
	 */
	private void setEatenByPlayer() { 
		setHazardous(false);
		setEdible(false);
		setHiddenWhileFrozen();
		setMoveMode(GameStateManager.NAV_MODE.GO_HOME);
		SoundEffect.killAllBackgroundSoundEffects();
		
		// This sound effect will trigger change in background sound once
		// it has played to completion.
		SoundEffect.EVENT_PACMAN_EATGHOST.play();
	}
	
	/*
	 * Reset Ghost 'consumed' state. This should be called after Ghost returns to pen.
	 */
	private void clearEatenByPlayer() { 
		setHazardous(true);
		setEdible(false);
	}
	
	/*
	 * Get the current pixel X coordinate that the Ghost is targeting.
	 */
	private int getTargetXCo() { return targetXCo; }
	
	/*
	 * Get the current pixel Y coordinate that the Ghost is targeting.
	 */
	private int getTargetYCo() { return targetYCo; }
	
	/*
	 * Check if the Ghost has reached the entrance to the pen.
	 */
	private boolean atFrontDoor() {
		if(this.targetXCo == this.getPixelXLocation() && this.targetYCo == this.getPixelYLocation()) {
			return true;
		}
		
		return false;
	}
	
	/*
	 * (Debug use only) Print ghost movement speed on-screen, and also paint
	 *  a colored rectangle at the x,y location that the ghost is currently targeting.
	 */
	@SuppressWarnings("unused")
	private void printDebugInfoOnScreen(Graphics2D g2d) {
		
		g2d.setColor(bodyColor);
		g2d.drawRect(targetXCo - (getWindowParams().getPixelScale() / 2),
				targetYCo - (getWindowParams().getPixelScale() / 2),
				getWindowParams().getPixelScale(), getWindowParams().getPixelScale());
		
		String pixelDistance = String.format("%d", (int)getPixelSpeed());
		
		g2d.setColor(Color.WHITE);
		g2d.setFont(textFont);
		
		
		g2d.drawString(pixelDistance, 
				this.getPixelXLocation() - (getWindowParams().getPixelScale() / 2), 
				this.getPixelYLocation() - (getWindowParams().getPixelScale() / 2));
	}
	
	//////////////////////////////////////////////////////////
	// Private member variables
	//////////////////////////////////////////////////////////
	
	private GameStateManager.NAV_MODE ghostMovementMode;
	private ArrayList<Integer> [] xLegPolygonPoints;
	private ArrayList<Integer> [] yLegPolygonPoints;
	private ArrayList<Integer> mouthStartPointsX;
	private ArrayList<Integer> mouthEndPointsX;
	private ArrayList<Integer> mouthStartPointsY;
	private ArrayList<Integer> mouthEndPointsY;
	private Color bodyColor;
	private int xPupilOffset[] = new int[4];
	private int yPupilOffset[] = new int[4];
	private int _eyeUpShift[] = new int[4];
	private int radius;
	private int legYTop;
	private int leftEyeX;
	private int leftEyeY;
	private int rightEyeX;
	private int rightEyeY;
	private int scaredLeftEyeX;
	private int scaredRightEyeX;
	private int scaredEyesWidth;
	private int scaredEyesHeight;
	private int eyeWidth;
	private int eyeHeight;
	private int legPositionCounter;
	private boolean legPosition;
	private boolean invertScaredColors;
	private String name;
	private int targetXCo;
	private int targetYCo;
	private Coordinate defaultTarget;
	private Ghost partner;
	private Font textFont;
}
